﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Configuration;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using Medianamik.Core.Configuration;
using Medianamik.Core.Interfaces;
using Medianamik.Core.Security;

namespace Medianamik.Core
{
    [DebuggerDisplay("{GetType()} : {NodeType.Name}, ID = {ID} ")]
    public class Node : HierarchicalEntity<Node>, Logging.ILoggable, INode, IMedianamikDTO
    {
        /// <summary>
        /// Ce constructeur est requis pour utiliser Node avec des méthodes génériques
        /// </summary>
        public Node()
        {

        }

        internal Node(Guid id)
        {
            ID = id;
        }

        internal Node(Guid id, Guid pTypeID, Guid? pParentID, string path, int? depth, bool isDeleted)
            : base(id, pParentID, path, depth)
        {
            NodeTypeID = pTypeID;
            IsDeleted = isDeleted;
        }

        public NodeInstance this[string pCultureName]
        {
            get
            {
                return InternalInstances[pCultureName];
            }
        }
        public NodeInstance this[CultureInfo pCulture]
        {
            get
            {
                return InternalInstances[pCulture];
            }
        }

        private Guid _nodeTypeID;
        public virtual Guid NodeTypeID
        {
            get { return _nodeTypeID; }
            internal set
            {
                //Ne pas changer le _nodeTypeId
                //Pour utilisation avec la syntaxe Object Initializer
                _nodeTypeID = value;
            }
        }

        private NodeType _nodeType;
        public NodeType NodeType
        {
            get
            {
                if (_nodeType == null)
                {
                    _nodeType = NodeTypeManager.GetNodeType(NodeTypeID);
                    if (_nodeType == null)
                    {
                        throw new CoreException("Unknown NodeType:" + NodeTypeID);
                    }
                }

                return _nodeType;
            }
        }

        internal BitVector32 Flags;

        private short _position;
        public Guid NodeId
        {
            get { return ID; }
            set { throw new NotImplementedException(); }
        }

        public Guid? ParentNodeId
        {
            get { return ParentID; }
            set { throw new NotImplementedException(); }
        }

        public Guid TypeId
        {
            get { return NodeTypeID; }
            set { throw new NotImplementedException(); }
        }

        public short Position
        {
            get
            {
                return _position;
            }
            set
            {
                _position = value;
                if (Status != EntityStates.New)
                    Status = EntityStates.Dirty;
            }
        }

        public bool IsActive
        {
            get { return DefaultInstance.IsActive; }
            set { throw new NotImplementedException(); }
        }

        public string CultureName
        {
            get { return DefaultInstance.Culture.Name; }
            set { throw new NotImplementedException(); }
        }

        public bool HasModifications
        {
            get { return DefaultInstance.HasModifiedProperties; }
            set { throw new NotImplementedException(); }
        }

        public int? Revision
        {
            get { return DefaultInstance.Revision; }
            set { throw new NotImplementedException(); }
        }

        public DateTime? ModifiedOn
        {
            get { return EditedOn; }
            set { throw new NotImplementedException(); }
        }

        /// <summary>
        /// Permet de départager les Noeuds ayant la même Position
        /// avec leur date CreatedOn
        /// </summary>
        /// <remarks>
        /// La valeur de TimeSpan.TotalSeconds est exprimée sur 40 bits maximum
        /// Position est shifté de 48 bits et forme les 16 bits de poids fort
        /// </remarks>
        public long LongPosition
        {
            get
            {
                return ((long)(DateTime.MaxValue - CreatedOn).TotalSeconds) | ((long)Position << 48);
            }
        }

        private int? _childNodeCount;
        public int ChildNodeCount
        {
            get
            {
                return _childNodeCount ?? (_childNodeCount = NodeManager.GetChildNodeCount(ID)) ?? 0;
            }
            internal set
            {
                _childNodeCount = value;
            }
        }

        public bool HasChildren
        {
            get
            {
                return ChildNodeCount > 0;
            }
        }

        private bool? _canHaveChildNodes;
        public virtual bool CanHaveChildNodes
        {
            get
            {
                if (_canHaveChildNodes == null)
                {
                    _canHaveChildNodes = NodeManager.NodeCanHaveChildren(_nodeTypeID);
                }

                return _canHaveChildNodes.GetValueOrDefault(false);
            }
        }

        private bool? _canHaveLegitimateChildNodes;
        public virtual bool CanHaveLegitimateChildNodes
        {
            get
            {
                if (!_canHaveLegitimateChildNodes.HasValue)
                {
                    var childs = LegitimateChildTypes.Where(t => Roles.UserHasRight(t, SecurityLevels.Create) && !t.IsSystem && !t.Listable);

                    if (WebSite != null || ConfigManager.GetAllWebSites(MedianamikContext.Current.EditionCulture).Count < 1)
                        childs = childs.Where(t => !t.ID.Equals(MedianamikTypes.WebSiteProxy));

                    _canHaveLegitimateChildNodes = childs.Count() > 0;
                }

                return _canHaveLegitimateChildNodes ?? false;
            }
        }
        public DateTime CreatedOn
        {
            get;
            set;
        }

        public int Generation
        {
            get { throw new NotImplementedException(); }
            set { throw new NotImplementedException(); }
        }

        public DateTime? EditedOn
        { get; internal set; }

        public bool BlockDirty { get; set; }

        public bool IsDeleted
        { get; internal set; }

        private bool? _isSubEntity;
        public bool IsSubEntity
        {
            get { return _isSubEntity.GetValueOrDefault(false); }
            internal set { _isSubEntity = value; }
        }

        public string EditedBy
        { get; internal set; }

        public string CreatedBy
        { get; internal set; }

        public bool IsDraft
        {
            get
            {
                return NodeType.IsVersioned && (HasInstanceForCurrentEditionCulture
                                                && DefaultInstance.HasModifiedProperties);
            }
        }
        public bool IsVersioned
        {
            get
            {
                return NodeType.IsVersioned;
            }
        }

        private Dictionary<DateTime, List<NodeProperty>> _nodePropertiesDictionary;
        internal Dictionary<DateTime, List<NodeProperty>> NodePropertiesDictionary
        {
            get
            {
                return _nodePropertiesDictionary
                    ?? (_nodePropertiesDictionary = new Dictionary<DateTime, List<NodeProperty>>());
            }
        }
        internal virtual List<NodeProperty> GetNodeProperties(DateTime? pRevision)
        {
            //On utilise int.MaxValue pour la  revision draft
            var key = pRevision ?? DateTime.MinValue;

            if (!NodePropertiesDictionary.ContainsKey(key))
            {
                var values = DALProviderManager.DALProvider.NodeDALProvider.GetNodePropertyValues(this, pRevision).ToList();
                _nodePropertiesDictionary.Add(key, new List<NodeProperty>(values));
            }

            return _nodePropertiesDictionary[key];
        }

        internal List<NodeProperty> GetNodePropertiesForNewInstance(string pCultureName, 
            DateTime? pRevision)
        {
            var invariantProps = GetNodeProperties(pRevision)
                .Where(p => p.Culture.Name == CultureInfo.InvariantCulture.Name);

            if (pCultureName == CultureInfo.InvariantCulture.Name)
            {
                return invariantProps.ToList();
            }

            var newProps = new NodePropertyFactory().CreateNodeProperties(this, pCultureName);

            return invariantProps.Concat(newProps).ToList();
        }

        private NodeInstanceCollection _instances;
        internal NodeInstanceCollection InternalInstances
        {
            get
            {
                if (_instances == null)
                {
                    _instances = new NodeInstanceCollection(this);
                }

                return _instances;
            }
        }
        public ReadOnlyNodeInstanceCollection Instances
        {
            get
            {
                return InternalInstances.AsReadOnly();
            }
        }
        /// <summary>
        /// Indique si la collection d'instance du Noeud est chargée.
        /// Ce Flag sert lors de la sauvegarde pour éviter de charger les instances inutilement
        /// </summary>
        public bool InstancesLoaded
        {
            get
            {
                return _instances != null;
            }
        }

        /// <summary>
        /// Instance pae défaut. En fonction de la propriété DefaultCulture
        /// </summary>
        public NodeInstance DefaultInstance
        {
            get
            {
                if (Instances.Count == 0)
                    throw new CoreException(String
                        .Format("L'élément {0} de type {1} n'a aucune instance.",
                        ID, NodeType.Name));
                CultureInfo cultureToSearch = DefaultCulture;
                if (String.Compare(DefaultCulture.TwoLetterISOLanguageName, "iv", StringComparison.InvariantCultureIgnoreCase) == 0)
                {
                    cultureToSearch = MedianamikContext.Current.CurrentUICulture;

                }
                if (Instances.Any(i => i.Culture.Name.Equals(cultureToSearch.Name, StringComparison.InvariantCultureIgnoreCase)))
                {
                    if (Instances[cultureToSearch].IsNew && String.IsNullOrEmpty(Instances[cultureToSearch].Name))
                    {
                        return Instances[0];
                    }

                    return Instances[cultureToSearch];
                }

                return Instances[0];
            }
        }

        private CultureInfo _defaultCulture;
        /// <summary>
        /// Culture par défaut du Noeud
        /// Overrider cette méthode si le Noeud à une culture Neutre
        /// </summary>
        public virtual CultureInfo DefaultCulture
        {
            get
            {
                return _defaultCulture
                    ?? (_defaultCulture = MedianamikContext.Current.EditionCulture);
            }
            set
            {
                _defaultCulture = value;
            }
        }

        //Collection filtrée pour tenir compte des règles hiérarchiques sur les noeuds
        private Collection<NodeType> _legitimateChildTypes;
        public Collection<NodeType> LegitimateChildTypes
        {
            get
            {
                if (_legitimateChildTypes == null)
                {
                    _legitimateChildTypes = new Collection<NodeType>();

                    foreach (var type in NodeType.LegitimateChildTypes)
                    {
                        if (type.LegitimateParentNodes.Count > 0)
                        {
                            if (type.LegitimateParentNodes.Exists(n => n.ID == ID))
                            {
                                _legitimateChildTypes.Add(type);
                            }
                        }
                        else
                        {
                            _legitimateChildTypes.Add(type);
                        }
                    }
                }

                return _legitimateChildTypes;
            }
        }

        public bool IsSecured { get { return Permissions.Count + InheritedPermissions.Count > 0; } }

        private List<InheritedPermission> _inheritedPermission;
        public ReadOnlyCollection<InheritedPermission> InheritedPermissions
        {
            get
            {
                if (_inheritedPermission == null)
                {
                    _inheritedPermission = SecurityManager.GetInheritedNodePermissionsByNodeId(ID).ToList();
                }
                return _inheritedPermission.AsReadOnly();
            }
        }

        private List<Permission> _permissions;
        public ReadOnlyCollection<Permission> Permissions
        {
            get
            {
                if (_permissions == null)
                {
                    _permissions = SecurityManager.GetNodePermissionsByNodeId(ID).ToList();
                }
                return _permissions.AsReadOnly();
            }
        }

        private Approbation _approbation;
        public Approbation Approbation
        {
            get
            {
                if(_approbation == null)
                {
                    _approbation = SecurityManager.GetNodeApprobationByNode(this);
                }
                return _approbation;
            }
        }

        private Guid? _namePropertyId;
        public Guid NamePropertyId
        {
            get
            {
                if (!_namePropertyId.HasValue)
                {
                    if (NodeType.InheritedPropertiesInternal.Exists("Name"))
                    {
                        _namePropertyId = NodeType.InheritedPropertiesInternal["Name"].ID;
                    }
                    else if (NodeType.PropertiesInternal.Exists("Name"))
                    {
                        _namePropertyId = NodeType.PropertiesInternal["Name"].ID;
                    }
                    else { _namePropertyId = Guid.Empty; }
                }
                return _namePropertyId.Value;
            }
            internal set
            {
                _namePropertyId = value;
            }
        }

        public bool HasName
        {
            get
            {
                return NamePropertyId != Guid.Empty;
            }
        }

        public bool HasUrl
        {
            get
            {
                return (NodeType.GetAllProperties()
                    .Any(p => p.Name.Equals("Url", StringComparison.InvariantCultureIgnoreCase)));
            }
        }

        public virtual string Name
        {
            get
            {
                var name = string.Empty;

                if (NodeTypeID.Equals(MedianamikTypes.MedianamikSyndication))
                {
                    var syndicatedNodeID = DefaultInstance.Properties[MedianamikProperties.SyndicatedNode].GetValue<Guid?>();
                    if (syndicatedNodeID.HasValue)
                    {
                        var syndicatedNode = NodeManager.GetNode(syndicatedNodeID.Value);
                        if (syndicatedNode != null)
                            name = syndicatedNode.Name;
                    }
                }
                else
                {
                    if (HasName)
                    {
                        name = DefaultInstance.Name;
                    }
                }

                if (name.IsNullOrEmpty())
                    name = ID.ToString();

                return name.IsNullOrEmpty() ? ID.ToString() : name;
            }
            set { throw new Exception("The name property of a node cannot be set that way."); }
        }

        //Doit afficher au moins l'ID - utile pour les treeview
        //Le displayName est complémentaire du Name.
        //Par exemple on peut faire un sort sur Name et afficher le DisplayName
        public virtual string DisplayName
        {
            get
            {
                //if (DefaultCulture != DefaultInstance.Culture)
                if (!Instances.Exists(DefaultCulture))
                {
                    return "[ " + Name + " ]";
                }
                if (Name == String.Empty)
                {
                    return ID.ToString();
                }

                return Name;
            }
        }

        private List<Breadcrumb> _breadcrumbs;
        public IEnumerable<Breadcrumb> Breadcrumbs
        {
            get
            {
                if (_breadcrumbs == null)
                {
                    _breadcrumbs = new List<Breadcrumb>();

                    if (FullHierarchy.Count() > 0)
                    {
                        _breadcrumbs.AddRange(FullHierarchy.ToList().
                            ConvertAll(n => new Breadcrumb(n.ID.ToString(), n.DisplayName)));
                    }
                }

                return _breadcrumbs;
            }
        }

        private List<Node> _fullHierarchy;
        protected IEnumerable<Node> FullHierarchy
        {
            get
            {
                if (_fullHierarchy == null)
                {
                    List<Node> nodes = NodeManager.GetParentNodes(this, null, true, 0);
                    nodes.Sort((n1, n2) => n1.Depth.CompareTo(n2.Depth));

                    if (nodes.Count > 0 && DefaultCulture != null)
                    {
                        nodes.ForEach(n => n.DefaultCulture = DefaultCulture);
                    }

                    _fullHierarchy = nodes;
                }

                return _fullHierarchy;
            }
        }

        public bool UseInChildUrls
        {
            get
            {
                bool defaultValue = false;
                if (DefaultInstance.Properties.Exists(MedianamikProperties.UseInChildUrls))
                    defaultValue = DefaultInstance.Properties[MedianamikProperties.UseInChildUrls].GetValue<bool?>() ?? true;
                return defaultValue;
            }
        }

        private bool _websiteLoaded;
        private WebSite _webSite;
        public virtual WebSite WebSite
        {
            get
            {
                if (_webSite == null && !_websiteLoaded)
                {
                    _webSite = NodeManager.GetParentNodes<WebSite>(this,
                        MedianamikTypes.WebSite, true, 0).FirstOrDefault();

                    if (_webSite == null)
                    {
                        var webSiteProxy = NodeManager.GetParentNodes(this,
                            MedianamikTypes.WebSiteProxy, true, 0).FirstOrDefault();

                        if (webSiteProxy != null)
                        {
                            var websiteId = webSiteProxy.DefaultInstance.Properties["WebSite"].GetValue<Guid?>();

                            if (websiteId.HasValue)
                                _webSite = NodeManager.GetNode<WebSite>(websiteId.Value);
                        }

                    }

                    if (_webSite != null && DefaultCulture != null)
                    {
                        _webSite.DefaultCulture = DefaultCulture;
                    }

                    _websiteLoaded = true;
                }

                return _webSite;
            }
        }

        public bool HasInstanceForCurrentEditionCulture
        {
            get
            {
                return Instances.Exists(MedianamikContext.Current.EditionCulture);
            }
        }

        public bool MayBeMoveTo(Guid? pParentNodeId)
        {
            if (pParentNodeId.HasValue)
            {
                Node parentNode = NodeManager.GetNode(pParentNodeId.Value);

                return !IsParentOf(parentNode) && RespectsNodeHierarchicalRules(pParentNodeId) &&
                    RespectsTypeHierarchicalRules(parentNode);
            }
            return RespectsNodeHierarchicalRules(pParentNodeId) &&
                   RespectsTypeHierarchicalRules(null);
        }

        //public bool IsLanguageNeutral()
        //{
        //    return Instances.Count <= 1 &&
        //           NodeType.GetAllProperties()
        //           .All(p => p.IsLanguageNeutral);
        //}

        public bool IsLanguageNeutral
        {
            get { return NodeType.IsLanguageNeutral; }
        }

        public NodeInstance AddNodeInstance(string pCultureName)
        {
            if (pCultureName == null) { throw new ArgumentNullException("pCultureName"); }
            if (pCultureName == "" && !NodeType.IsLanguageNeutral)
            {
                throw new ApplicationException(String.
                    Format("Cannot add language neutral instance to non neutral language node. ID: {0} Type: {1}",
                    ID, NodeType.Name));
            }

            if (pCultureName != "" && NodeType.IsLanguageNeutral)
            {
                throw new ApplicationException(String.
                    Format("Cannot add non language neutral instance to neutral language node. ID: {0} Type: {1}",
                    ID, NodeType.Name));
            }

            var instance = new NodeInstance(this, pCultureName) { IsNew = true };

            instance.InternalProperties = new NodePropertyCollection(instance, GetNodePropertiesForNewInstance(pCultureName, null));
            InternalInstances.Add(instance);
            return instance;
        }

        public void AddNodeInstances(IEnumerable<string> cultureNames)
        {
            var cultures = new List<string>(cultureNames);

            if (cultures.Count > 0 && cultures.Any(c => c == ""))
                cultures.Remove("");

            foreach (var cultureName in cultures)
            {
                if (!InternalInstances.Exists(cultureName))
                    AddNodeInstance(cultureName);
            }
        }

        public void RemoveNodeInstance(string pCultureName)
        {
            if (pCultureName == null) { throw new ArgumentNullException("pCultureName"); }
            if (InternalInstances.Exists(pCultureName))
            {
                InternalInstances.Remove(InternalInstances[pCultureName]);
                Status = EntityStates.Dirty;
            }
            else
            {
                throw new Exception("No existing instance for: " + pCultureName);
            }
        }

        public bool RespectsTypeHierarchicalRules()
        {
            return RespectsTypeHierarchicalRules(ParentNode);
        }

        public bool RespectsTypeHierarchicalRules(Node pParentNode)
        {
            if (NodeType.IsManyToMany)
                return true;

            if (NodeType.IsUnrestricted)
                return true;

            if (NodeType.Listable)
                return true;

            if (pParentNode == null) //À la racine
            {
                return NodeType.LegitimateParentTypes.Any(nt => nt.ID == Guid.Empty);
            }

            return pParentNode.LegitimateChildTypes.Any(nt => nt.ID == NodeTypeID);

            //return true;
        }

        public bool RespectsNodeHierarchicalRules()
        {
            return RespectsNodeHierarchicalRules(ParentID);
        }
        internal bool RespectsNodeHierarchicalRules(Guid? pParentID)
        {
            if (NodeType.IsManyToMany)
                return true;

            if (NodeType.LegitimateParentNodes.Count > 0)
            {
                if (pParentID.HasValue)
                    return NodeType.LegitimateParentNodes.Any(n => n.ID == pParentID.Value);
                return false;
            }

            return true;
        }

        public bool IsParentOf(Node childNode)
        {
            if (childNode == null)
                return false;

            return childNode.Path.IndexOf(ID.ToString(), StringComparison.OrdinalIgnoreCase) > -1;
        }

        public bool IsChildOf(Node parentNode)
        {
            if (parentNode == null)
                return false;

            return Path.IndexOf(parentNode.ID.ToString(), StringComparison.OrdinalIgnoreCase) > -1;
        }
        public bool IsChildOf(Guid parentNodeId)
        {
            if (parentNodeId == Guid.Empty)
                return false;

            return Path.IndexOf(parentNodeId.ToString(), StringComparison.OrdinalIgnoreCase) > -1;
        }

        public IDictionary<string, T> GetValues<T>(string propertyName)
        {
            IDictionary<string, T> values = new Dictionary<string, T>();

            NodeTypeProperty property = NodeType.GetAllProperties()
                 .First(p => p.Name.Equals(propertyName, StringComparison.OrdinalIgnoreCase));

            if (property.IsLanguageNeutral)
                values.Add("", Instances[0].Properties[propertyName].GetValue<T>());
            else
                Instances.Where(i => i.Culture.Name != "").ForEach(i => values.Add(i.Culture.Name, i.Properties[propertyName].GetValue<T>()));

            return values;
        }

        public IEnumerable<Node> GetChildContentNodes(CultureInfo pCulture)
        {
            return GetChildContentNodes(pCulture, false);
        }

        public IEnumerable<Node> GetChildContentNodes(CultureInfo pCulture, bool inclusive)
        {
            List<Node> nodes = NodeManager.GetChildNodes(ID, inclusive, 1);

            nodes.Sort((n1, n2) => n1.LongPosition.CompareTo(n2.LongPosition));

            if (nodes.Count > 0 && pCulture != null)
            {
                nodes.ForEach(n => n.DefaultCulture = pCulture);
            }

            return nodes.AsEnumerable();
        }

        public IEnumerable<Node> ParentNodes
        {
            get
            {
                return FullHierarchy
                    .Where(n => n.ID != ID);
            }
        }

        private Node _parentNode;
        public Node ParentNode
        {
            get
            {
                if (_parentNode == null && ParentID.HasValue)
                {
                    if (_fullHierarchy != null && ParentNodes.Count() > 0)
                    {
                        _parentNode = ParentNodes.SingleOrDefault(n => n.ID.Equals(ParentID.Value));
                    }
                    else
                    {
                        _parentNode = NodeManager.GetNode(ParentID.Value);
                    }

                    if (_parentNode != null)
                    {
                        _parentNode.DefaultCulture = DefaultCulture;
                    }
                }

                return _parentNode;
            }
        }

        internal void MarkSaved()
        {
            Status = EntityStates.Loaded;
            foreach (var inst in InternalInstances)
            {
                inst.MarkSaved();


            }

            var m2MProps = Instances.Where(i => i.PropertiesLoaded).SelectMany(inst => inst.Properties.Where(p => !p.IsOwnProperty))
                                            .OfType<ManyToManyNodeProperty>().Distinct().Where(m => m.ForeignProperty.PrimaryMedianamikControlID.HasValue).ToList();
            m2MProps.SelectMany(p => p.GetCurrentRelationsValues()).ForEach(n => n.InnerNode.MarkSaved());
        }

        Guid? Logging.ILoggable.LogObjectID
        {
            get
            {
                return ID;
            }
        }
        string Logging.ILoggable.LogObjectName
        {
            get
            {
                return Name;
            }
        }
        Logging.LogEntryType Logging.ILoggable.LogType
        {
            get
            {
                if (NodeType.ID == MedianamikTypes.MedianamikTemplate)
                    return Logging.LogEntryType.Template;
                return Logging.LogEntryType.Node;
            }
        }

        //public bool Equals(Node other)
        //{
        //    if (ReferenceEquals(other, null))
        //    {
        //        return false;
        //    }
        //    if (ReferenceEquals(other, this))
        //    {
        //        return true;
        //    }
        //    return (ID == other.ID);
        //}

        //public override bool Equals(object obj)
        //{
        //    return Equals((Node)obj);
        //}

        public override int GetHashCode()
        {
            return ID.GetHashCode();
        }

        protected override Node GetParent(Guid parentId)
        {
            return NodeManager.GetNode(parentId);
        }
    }
}